# Convenient utlities using Gtk
#
# Copyright (C) 2010 Yotam Medini <yotam.medini@gmail.com>
#
# This file is part of Gupicasa.
#
# Gupicasa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Gupicasa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gupicasa.  If not, see <http://www.gnu.org/licenses/>.


import gtk
import string
import sys
import xml.parsers.expat


import debug


class StockButton(gtk.Button):


    def __init__(self, stockid, label=None):
        gtk.Button.__init__(self)
        img = gtk.Image()
        img.set_from_stock(stockid, gtk.ICON_SIZE_BUTTON)
        self.set_image(img)
        if label is not None:
            self.set_label(label)


class IntegerSpinButton(gtk.SpinButton):


    def __init__(self, min_value=0, max_value=0xff):
        adj = gtk.Adjustment(value=min_value, lower=min_value, upper=max_value,
                             step_incr=1, page_incr=1)
        # digits = max(len("%d" % min_value), len("%d" % max_value))
        gtk.SpinButton.__init__(self, adjustment=adj)

    def iget_value(self):
        return int(self.get_value())


class RadioToggleButtons:


    def __init__(self):
        self.current_active = None

    def add(self, b):
        b.connect('clicked', self.click_cb, None)


    def click_cb(self, b, dummy):
        if b.get_active():
            if self.current_active is not None and self.current_active != b:
                self.current_active.set_active(False)
            self.current_active = b


class FrameBWC(gtk.Frame):
    "Frame with border-width and container"

    def __init__(self, label, bw, container):
        gtk.Frame.__init__(self, label)
        self.set_border_width(bw)
        self.c = container
        self.add(container)


class FitNativeZoomControl:


    def __init__(self, box, cb, data=None):


        self.cb = cb
        self.data = data
        for stock in (gtk.STOCK_ZOOM_FIT, gtk.STOCK_ZOOM_100):
            img = gtk.image_new_from_stock(stock, gtk.ICON_SIZE_BUTTON)
            tb = gtk.ToggleButton()
            tb.add(img)
            if stock == gtk.STOCK_ZOOM_FIT:
                tb.set_tooltip_text("Scale detailed image\nto fit window")
            else:
                tb.set_tooltip_text(
                    "Scale detailed image\nin native pixels size")
            box.pack_start(tb, expand=False, fill=False, padding=1)
            tb.connect('clicked', self.click_cb, stock == gtk.STOCK_ZOOM_FIT)
            tb.show_all()


    def click_cb(self, w, fit_flag):
        self.cb(fit_flag, self.data)


class SimpleStatusbar(gtk.Statusbar):


    def __init__(self):
        gtk.Statusbar.__init__(self)


    def set_text(self, msg):
        self.pop(0)
        self.push(0, msg)


class Monitor(gtk.Dialog):

    def __init__(self, title, parent, step_cb, status_widgets=[]):
        flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
        gtk.Dialog.__init__(self, title=title, parent=parent, flags=flags,
                            buttons=())
        self.step_cb = step_cb
        self.canceled = False

        stock = gtk.image_new_from_stock(gtk.STOCK_DIALOG_INFO,
                                         gtk.ICON_SIZE_DIALOG)
        hb_main = gtk.HBox()
        vbox_status = gtk.VBox()
        self.local_status = local_status = gtk.Label("Monitoring...")
        self.external_status = external_status = gtk.Label("")
        vbox_status.pack_start(local_status, expand=True, fill=True, padding=2)
        vbox_status.pack_start(external_status, expand=True, fill=True,
                               padding=2)
        hb_main.pack_start(stock, expand=False, fill=False, padding=2)
        hb_main.pack_start(vbox_status, expand=True, fill=True, padding=2)
        self.status_widgets = [local_status] + status_widgets
        self.vbox.pack_start(hb_main, expand=True, fill=True, padding=0)

        bcancel = StockButton(gtk.STOCK_CANCEL)
        bcancel.connect('clicked', self.cancel)
        bcancel.set_tooltip_text("Cancel execution in the middle")
        aa = self.get_action_area()
        aa.pack_start(bcancel, expand=False, fill=False, padding=1)


    def run_monitor(self):
        self.show_all()
        status_text = "Monitoring run..."
        while status_text is not None and not self.canceled:
            map(lambda w: w.set_text(status_text), self.status_widgets)
            gtk_update()
            status_text = self.step_cb()
        done = not self.canceled
        return done


    def cancel(self, w):
        debug.log("")
        self.local_status.set_text("Canceled")
        w.set_sensitive(False) # self.step_cb() may still be working
        self.canceled = True


def warning(parent, message):
    flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
    dlg =  gtk.MessageDialog(parent=parent, flags=flags,
                             type=gtk.MESSAGE_WARNING,
                             buttons=gtk.BUTTONS_CLOSE,
                             message_format=message)
    dlg.show()
    dlg.run() # wait for ignored response
    dlg.destroy()


def gtk_update():
    while gtk.events_pending():
        gtk.main_iteration()


def select_fs_directory(title, parent, icon=None):
    debug.log("")
    fsdir = None
    buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
               gtk.STOCK_OPEN, gtk.RESPONSE_OK)
    dlg = gtk.FileChooserDialog(
        title=title,
        parent=parent,
        action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
        buttons=buttons)
    if icon is not None:
        dlg.set_icon(icon)
    response = dlg.run()
    debug.log("response=%d, e=%s" % (response, dlg.get_filename()))
    if response == gtk.RESPONSE_OK:
        fsdir = dlg.get_filename()
    dlg.destroy()
    return fsdir


def add_fit_native_zoom_control(box, cb, data=None):
    c = FitNativeZoomControl(box, cb, data)


def show_flag_widgets(flag, widgets):
    if flag:
        map(lambda w: w.show(), widgets)
    else:
        map(lambda w: w.hide(), widgets)


def attach1(tbl, w, c, r,
            xoptions=gtk.EXPAND|gtk.FILL, yoptions=gtk.EXPAND|gtk.FILL,
            xpadding=1, ypadding=1, pos=None):
    if pos is not None: # Align to one of 4 positions
        b = None
        filler = gtk.Label("")

        if pos in (gtk.POS_LEFT, gtk.POS_RIGHT):
            b = gtk.HBox()
        elif pos in (gtk.POS_TOP, gtk.POS_BOTTOM):
            b = gtk.VBox()

        if pos in (gtk.POS_LEFT, gtk.POS_TOP):
            b.pack_start(w, expand=False, fill=False, padding=0)
            b.pack_start(filler, expand=True, fill=True, padding=0)
        elif pos in (gtk.POS_RIGHT, gtk.POS_BOTTOM):
            b.pack_start(filler, expand=True, fill=True, padding=0)
            b.pack_start(w, expand=False, fill=False, padding=0)

        if b is not None:
            w = b
    tbl.attach(w, c, c + 1, r, r + 1, xoptions=xoptions, yoptions=yoptions,
               xpadding=xpadding, ypadding=ypadding)


(ALIGN_LEFT_TOP, ALIGN_CENTER, ALIGN_RIGHT_BOTTOM) = range(3)
ALIGN_LEFT = ALIGN_LEFT_TOP
ALIGN_TOP = ALIGN_LEFT_TOP
ALIGN_RIGHT = ALIGN_RIGHT_BOTTOM
ALIGN_BOTTOM = ALIGN_RIGHT_BOTTOM

def pack_start_align(box, w, align, expand=False, fill=False, padding=0):
    cbox = (gtk.VBox() if isinstance(box, gtk.HBox) else gtk.HBox())
    if align in (ALIGN_CENTER, ALIGN_RIGHT_BOTTOM):
        cbox.pack_start(gtk.Label(), expand=True, fill=True, padding=0)
    cbox.pack_start(w, expand=False, fill=False, padding=0)
    if align in (ALIGN_CENTER, ALIGN_LEFT_TOP):
        cbox.pack_start(gtk.Label(), expand=True, fill=True, padding=0)
    box.pack_start(cbox, expand=expand, fill=fill, padding=padding)


def _get_widget_tree_rec(w, paths, curr_path):
    try:
        children = w.get_children()
    except:
        children = []
    for c in children:
        if c.name is not None:
            cpath = curr_path + [c.name]
            paths.append(cpath)
            _get_widget_tree_rec(c, paths, cpath)


def get_widget_tree(w):
    "return list of paths to children widgets"
    paths = []
    _get_widget_tree_rec(w, paths, [])
    return paths


class _UI_Parser:


    def __init__(self, ui_string):
        self.paths = []
        self.current_path = []
        self.estack = [] # Similar to current_path, but may have None-s.
        p = xml.parsers.expat.ParserCreate()
        p.StartElementHandler = self.start_element
        p.EndElementHandler = self.end_element
        p.Parse(ui_string)


    def start_element(self, name, attrs):
        e = attrs.get('action', None)
        if e is not None:
            self.paths.append(self.current_path + [e])
        else:
            e = attrs.get('name', None)
        self.estack.append(e)
        if e is not None:
            self.current_path.append(e)
        pass


    def end_element(self, name):
        e = self.estack.pop()
        if e is not None:
            self.current_path.pop()


def ui_get_action_paths(ui_string):
    parser = _UI_Parser(ui_string)
    paths = map(lambda p: "/" + string.join(p, '/'), parser.paths)
    return paths


def uimgr_set_tips(uimgr):
    ui_info = uimgr.get_ui()
    actions_paths = ui_get_action_paths(ui_info)
    for p in actions_paths:
        widget = uimgr.get_widget(p)
        action = uimgr.get_action(p)
        tip = action.get_tooltip() if action else None
        if not None in (widget, tip):
            # debug.log("widget=%s, action=%s" % (widget, action))
            debug.log("p=%s, tip=%s" % (p, tip))
            widget.set_tooltip_text(tip)


def image_data_to_pixbuf(image_data):
    debug.log("len(image_data)=%d" %
              -1 if image_data is None else len(image_data))
    pbl = gtk.gdk.PixbufLoader()
    try:
        pbl.write(image_data)
    except Except, e:
        debug.log("Pixbuf from image_data failed: %s" % str(e))
    pbl.close()
    pixbuf = pbl.get_pixbuf()
    debug.log("(!=None)?: image_data=%d, pixbuf=%d" %
              ((image_data is not None), (pixbuf is not None)))
    return pixbuf
